package com.redisson.handler;

import com.redisson.annotate.RateLimiter;
import com.redisson.annotate.RateLimiters;
import com.redisson.aop.RateLimiterKeyProvider;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;

import java.nio.charset.StandardCharsets;

/**
 * 背后逻辑：<a href="https://blog.csdn.net/qq_37477317/article/details/139993448">...</a>
 */
@Slf4j
@Component
@Aspect
public class RateLimiterAspectHandler {

    @Autowired
    private RateLimiterKeyProvider keyProvider;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 单条件限流控制器
     * @param point
     * @param rateLimiter
     * @return
     * @throws Throwable
     */
    @Around(value = "@annotation(rateLimiter)", argNames = "point,rateLimiter")
    public Object around(ProceedingJoinPoint point, RateLimiter rateLimiter) throws Throwable {
        isAllow(point, rateLimiter);
        return point.proceed();
    }

    /**
     * 多条件限流控制器
     * @param point
     * @param rateLimiters
     * @return
     * @throws Throwable
     */
    @Around(value = " @annotation(rateLimiters)", argNames = "point,rateLimiters")
    public Object around(ProceedingJoinPoint point, RateLimiters rateLimiters) throws Throwable {
        RateLimiter[] limiters = rateLimiters.value();
        for (RateLimiter rateLimiter : limiters) {
            isAllow(point, rateLimiter);
        }
        return point.proceed();
    }

    private void isAllow(ProceedingJoinPoint point, RateLimiter rateLimiter) {
        // 获取key
        String key = keyProvider.getKey(point, rateLimiter);
        // 此处是为了日志输出
        boolean flag = StringUtils.isNotBlank(key);
        // 类路径+方法，然后计算md5
        String uniqueKey = getUniqueKey((MethodSignature) point.getSignature());
        // key名称
        key = StringUtils.isNotBlank(key) ? uniqueKey + "." + key : uniqueKey;
        // 获取RRateLimiter实例
        RRateLimiter limiter = redissonClient.getRateLimiter(key);
        // 创建令牌桶数据模型，单位时间内产生多少令牌
        limiter.trySetRate(rateLimiter.mode(), rateLimiter.rate(), rateLimiter.rateInterval(), RateIntervalUnit.MINUTES);
        // 尝试访问数据，timeout 时间内，允许获得的数量permits(如果获取失败，返回false)
        // 单位时间内不能获取到1个令牌，则返回，不阻塞
        boolean tryAcquire = limiter.tryAcquire(10, rateLimiter.rateInterval(), rateLimiter.rateIntervalUnit());
//        boolean tryAcquire = limiter.tryAcquire(1);
        log.info("【{}】请求，线程：【{}】，获取令牌的结果：{}", flag ? "单用户" : "多用户", Thread.currentThread().getName(), tryAcquire);
        while (!tryAcquire) {
            log.error("限流模式：{}; 限流数量：{}; 限流时间间隔：{}{}",
                    rateLimiter.mode().toString(), rateLimiter.rate(), rateLimiter.rateInterval(),rateLimiter.rateIntervalUnit());
//            throw new RateLimiterException(rateLimiter.showPromptMsg());
            try {
                Thread.sleep(500);
            }catch (Exception e){
                log.error("限流睡眠异常", e);
            }
//            limiter = redissonClient.getRateLimiter(key);
//            // 创建令牌桶数据模型，单位时间内产生多少令牌
//            limiter.trySetRate(rateLimiter.mode(), rateLimiter.rate(), rateLimiter.rateInterval(), RateIntervalUnit.MINUTES);
            tryAcquire = limiter.tryAcquire(10, rateLimiter.rateInterval(), rateLimiter.rateIntervalUnit());
            log.info("while中【{}】请求，线程：【{}】，获取令牌的结果：{}", flag ? "单用户" : "多用户", Thread.currentThread().getName(), tryAcquire);
        }
    }

    private String getUniqueKey(MethodSignature signature) {
        String format = String.format("%s.%s", signature.getDeclaringTypeName(), signature.getMethod().getName());
        return DigestUtils.md5DigestAsHex(format.getBytes(StandardCharsets.UTF_8));
    }

}
